﻿using System;
using System.Xml;
using System.Collections;
using System.Runtime.Remoting;

namespace XmlParser
{
    public class XmlObject : IXmlObject
    {
        #region DataElement

        // Cross Refrances
        protected string iRootElementName;
        protected string iAssemblyFullName;
        protected string iAssemblyNamespace;
        protected IXmlObject iXmlObject;
        protected XmlNode iXmlNode;

        // Persistent Data		
        protected string v_Name = "", v_Value = "";
        protected Hashtable v_Attributes;
        protected Hashtable v_ChildrenNodes;

        #endregion DataElement

        #region Constructors

        /// <summary>
        /// To dynamically create an XmlObject for a specified XmlElement. 
        /// The class should be in the namespace "System.Xml.(RootElementName)."
        /// and the name of class should be prefixed with "Xml". (Example :: "System.Xml.(RootElementName).Xml(SomeNodeName)")
        /// The ClassName and the ElementName are case sensitive and should be in same case.
        /// </summary>
        /// <param name="ElementName"></param>
        /// <param name="NewAssemblyFullName"></param>
        /// <param name="NewRootElementName"></param>
        /// <returns></returns>
        public static IXmlObject CreateInstance(string ElementName, string NewAssemblyFullName, string NewRootElementName, string Namespace)
        {
            ObjectHandle objHnd = Activator.CreateInstance(NewAssemblyFullName, Namespace + NewRootElementName + ".Xml" + ElementName);
            IXmlObject IXmlObject1 = (IXmlObject)objHnd.Unwrap();

            IXmlObject1.RootElementName = NewRootElementName;
            IXmlObject1.AssemblyFullName = NewAssemblyFullName;
            IXmlObject1.AssemblyNamespace = Namespace;

            return IXmlObject1;
        }

        /// <summary>
        /// Constructs default XmlObject Instance.
        /// </summary>
        public XmlObject()// should be public
        {
            v_Attributes = new Hashtable();
            v_ChildrenNodes = new Hashtable();
        }

        #endregion Constructors

        #region Processors

        /// <summary>
        /// Process the node as specified 
        /// </summary>
        /// <param name="NewXmlNode">The Node to be Processed.</param>
        internal override void ProcessNode(XmlNode NewXmlNode)
        {
            this.iXmlNode = NewXmlNode;
            this.Name = this.iXmlNode.Name;

            //According to XML standards, there should not be any Text if a node contains one or more children nodes
            //if(this.iXmlNode.ChildNodes.Count < 2 && this.iXmlNode.ChildNodes.Count > 0) --> "this.iXmlNode.ChildNodes.Count == 1"
            if (this.iXmlNode.ChildNodes.Count == 1 && this.iXmlNode.FirstChild.NodeType == XmlNodeType.Text)
                this.Value = this.iXmlNode.InnerText.Trim();
            else
                this.Value = "";

            foreach (XmlAttribute iXmlAttribute in this.iXmlNode.Attributes)
            {
                if (!(this.v_Attributes.ContainsKey(iXmlAttribute.Name)))
                    this.v_Attributes.Add(iXmlAttribute.Name, "");
                this.SetAttribute(iXmlAttribute.Name, iXmlAttribute.Value);
            }

            foreach (XmlNode cXmlNode in this.iXmlNode.ChildNodes)
            {
                if (cXmlNode.NodeType == XmlNodeType.Element)
                {
                    if (!(this.v_ChildrenNodes.ContainsKey(cXmlNode.Name.ToUpper())))
                        this.v_ChildrenNodes.Add(cXmlNode.Name.ToUpper(), new Hashtable());
                    this.ProcessChildNode(cXmlNode);
                }
            }
        }

        /// <summary>
        /// recursivly Process all the child Nodes of the Node specified.
        /// It is virtual so that you can provide your own implementation in addition to/or with this implementation.
        /// </summary>
        /// <param name="NewXmlNode">The Node whose child node is required to be Processed.</param>
        internal override void ProcessChildNode(XmlNode NewXmlNode)
        {
            this.iXmlObject = XmlObject.CreateInstance(NewXmlNode.Name, this.AssemblyFullName, this.RootElementName, this.AssemblyNamespace);
            Hashtable ht = (Hashtable)this.v_ChildrenNodes[NewXmlNode.Name.ToUpper()];

            ht.Add(ht.Count, this.iXmlObject);
            iXmlObject.ProcessNode(NewXmlNode);
        }

        #endregion Processors

        #region DeepClone

        /// <summary>
        /// 
        /// </summary>
        /// <returns>Key of newly added Object</returns>
        public override IXmlObject Duplicate()
        {
            IXmlObject NewXmlObject = XmlObject.CreateInstance(this.Name, this.AssemblyFullName, this.RootElementName, this.AssemblyNamespace);
            NewXmlObject.ProcessNode(this.iXmlNode);
            return NewXmlObject;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="NewXmlObject"></param>
        /// <returns></returns>
        protected override bool AddChildNode(IXmlObject NewXmlObject)
        {
            Hashtable ht = null;
            if (!this.v_ChildrenNodes.ContainsKey(NewXmlObject.Name.ToUpper()))
                this.ChildrenNodesAll.Add(NewXmlObject.Name.ToUpper(), new Hashtable());
            ht = this.ChildrenNodes(NewXmlObject.Name);
            ht.Add(ht.Count, NewXmlObject);
            return true;
        }

        protected override bool AddChildNodeAt(IXmlObject NewXmlObject, int AtIndex)
        {
            Hashtable ht = null;
            if(!this.v_ChildrenNodes.ContainsKey(NewXmlObject.Name.ToUpper()))
                this.ChildrenNodesAll.Add(NewXmlObject.Name.ToUpper(), new Hashtable());
            ht = this.ChildrenNodes(NewXmlObject.Name);

            if (AtIndex == ht.Count)
            {
                ht.Add(ht.Count, NewXmlObject);
            }
            else
            {
                Hashtable newHt = new Hashtable();
                for (int i = 0; i < ht.Count; i++)
                {
                    if (i == AtIndex)
                        newHt.Add(newHt.Count, NewXmlObject);
                    newHt.Add(newHt.Count, ht[i]);
                }
                v_ChildrenNodes.Remove(NewXmlObject.Name.ToUpper());
                v_ChildrenNodes.Add(NewXmlObject.Name.ToUpper(), newHt);
            }
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="NewXmlObject"></param>
        /// <returns></returns>
        protected override bool RemoveChildNode(IXmlObject NewXmlObject)
        {
            if (NewXmlObject == null)
                return false;
            Hashtable ht = this.ChildrenNodes(NewXmlObject.Name);
            if (ht == null)
                return false;
            IEnumerator VEnum = ht.Values.GetEnumerator();
            IEnumerator KEnum = ht.Keys.GetEnumerator();
            object XmlObjKey = null;

            while (VEnum.MoveNext())
            {
                KEnum.MoveNext();
                XmlObjKey = KEnum.Current;
                IXmlObject XmlObj = (IXmlObject)VEnum.Current;

                if (XmlObj.Equals(NewXmlObject))
                {
                    break;
                }
            }

            ht.Remove(XmlObjKey);

            Hashtable htNew = new Hashtable();
            Hashtable htOld = this.ChildrenNodes(NewXmlObject.Name);
            int index = 0;

            for (int i = 0; i < ht.Count + 1; i++)
            {
                object obj = ht[i];

                if (obj != null)
                {
                    htNew.Add(index, obj);
                    index++;
                }
            }

            this.v_ChildrenNodes.Remove(NewXmlObject.Name.ToUpper());
            this.v_ChildrenNodes.Add(NewXmlObject.Name.ToUpper(), htNew);

            return true;
        }

        #endregion DeepClone

        #region Properties

        /// <summary>
        /// Sets the value of an attribute.
        /// </summary>
        /// <param name="AttributeName">Name of Attribute.</param>
        /// <param name="AttributeValue">Value of Attribute.</param>
        protected override void SetAttribute(string AttributeName, string AttributeValue)
        {
            this.v_Attributes[AttributeName] = AttributeValue == null ? "" : AttributeValue;
        }

        /// <summary>
        /// To get the value of attribute specified by "AttributeName" parameter
        /// </summary>
        /// <param name="AttributeName">Name of the Attribute.</param>
        /// <returns>Value of the Attribute specified in "AttributeName" parameter.</returns>
        protected override string GetAttribute(string AttributeName)
        {
            return this.v_Attributes[AttributeName].ToString();
        }

        /// <summary>
        /// To produce Xml representation of XmlObject
        /// </summary>
        /// <returns>String representing Xml of XmlObject</returns>
        public override string ToXmlString()
        {
            string TagName = this.Name.ToString().Trim();
            string AttributesPart = "", AtribKey = "", AtribValue = "";
            IEnumerator AtribKeysEnum = this.Attributes.Keys.GetEnumerator();

            while (AtribKeysEnum.MoveNext())
            {
                AtribKey = AtribKeysEnum.Current.ToString();
                AtribValue = this.Attributes[AtribKey].ToString();
                AttributesPart = AttributesPart + " " + AtribKey + "=\"" + AtribValue + "\"";
            }

            string ChildrenPart = "", key = "";
            int key2 = 0;
            IEnumerator ChildrenNodesKeysEnum = this.ChildrenNodesAll.Keys.GetEnumerator();

            while (ChildrenNodesKeysEnum.MoveNext())
            {
                key = ChildrenNodesKeysEnum.Current.ToString();
                Hashtable Nodes = ((Hashtable)this.ChildrenNodesAll[key]);
                IEnumerator NodesEnum = Nodes.Keys.GetEnumerator();

                while (NodesEnum.MoveNext())
                {
                    key2 = (int)NodesEnum.Current;//.ToString();
                    XmlObject zXmlObject = ((XmlObject)Nodes[key2]);
                    ChildrenPart = zXmlObject.ToXmlString() + ChildrenPart;
                }
            }

            string ValuePart = this.Value.ToString() + "";

            string XmlString = "<" + TagName + " " + AttributesPart + ">" +
                ValuePart +
                ChildrenPart +
                "</" + TagName + ">\n";

            return XmlString;
        }

        /// <summary>
        /// 
        /// </summary>
        protected override XmlNode XmlNodeCurrent
        {
            get { return this.iXmlNode; }
        }

        /// <summary>
        /// Name of the XmlElement (TagName).
        /// </summary>
        public override string Name
        {
            get { return this.v_Name; }
            set { this.v_Name = value == null ? "" : value; }
        }

        /// <summary>
        /// String between opening and closing tag of XmlElement.
        /// </summary>
        public override string Value
        {
            get { return this.v_Value; }
            set { this.v_Value = value == null ? "" : value; }
        }

        /// <summary>
        /// Conains Attribute's name value pairs.
        /// </summary>
        protected override Hashtable Attributes
        {
            get { return this.v_Attributes; }
        }

        /// <summary>
        /// Represent the Root Node's Name of the document.
        /// It will always be a single static value for all node of One Xml Document.
        /// </summary>
        public override string RootElementName
        {
            get { return this.iRootElementName; }
            set { this.iRootElementName = value; }
        }

        public override string AssemblyFullName
        {
            get { return this.iAssemblyFullName; }
            set { this.iAssemblyFullName = value; }
        }

        public override string AssemblyNamespace
        {
            get
            {
                return this.iAssemblyNamespace;
            }
            set
            {
                this.iAssemblyNamespace = value;
            }
        }

        /// <summary>
        /// Contains Hashtables one for each ChildNode of one type.
        /// </summary>
        protected override Hashtable ChildrenNodesAll
        {
            get { return this.v_ChildrenNodes; }
        }
        /// <summary>
        /// Provide Hash table for a selected type of Child Node
        /// </summary>
        /// <param name="XmlElementName">
        /// Name of an Xml Node representing Type of the child nodes required 
        /// </param>
        /// <returns>Hashtable of Child Nodes for a selected type of Child Node.</returns>
        protected override Hashtable ChildrenNodes(string XmlElementName)
        {
            if (this.v_ChildrenNodes.ContainsKey(XmlElementName.ToUpper()))
            {
                return ((Hashtable)this.v_ChildrenNodes[XmlElementName.ToUpper()]);
            }
            else
            {
                //throw new IndexOutOfRangeException();
                return null;
            }
        }

        #endregion Properties
    }
}
